//
//  OIEmulator.m
//  OrchIDE
//
//  Created by local22 on 8/13/07.
//  Copyright 2007 __MyCompanyName__. All rights reserved.
//

#import "OIEmulator.h"
#import "emulator.h"

NSString *OIEmulatorDidWriteMemoryNotificationName = @"OIEmulatorDidWriteMemory";


@interface OIEmulator ()
- (void)readByteFromSerialPort:(uint8_t)byte;

- (void)setCurrentState:(struct state *)state;
- (struct state *)nextState;
@end

void OIEmulatorSerialPortCallback(uint8_t byte, void *context)
{
	id emulator = (id)context;
	[emulator readByteFromSerialPort:byte];
}

@implementation OIEmulator
- (id)init
{
	self = [super init];
	if(!self) {
		return nil;
	}
	
	_machine = create_machine();
	initialize_state(_machine);
	[self setCurrentState:get_state(_machine)];
	recv_serial_bytes(_machine, OIEmulatorSerialPortCallback, self);
	_cyclesPerRTCTick = 10000;
	
	return self;
}

- (void)dealloc
{
	destroy_machine(_machine);

	[super dealloc];
}

- (void)setCurrentState:(struct state *)currentState
{
	BOOL didR0Change, didR1Change, didR2Change, didR3Change, didR4Change, didR5Change, didR6Change, didR7Change, didUserModeChange, didFlagChange, didExecutingChange, didRSerialChange, didInstructionChange, didExceptionStateChange;
	
	didR0Change = !_currentState || _currentState->_r[0] != currentState->_r[0];
	didR1Change = !_currentState || _currentState->_r[1] != currentState->_r[1];
	didR2Change = !_currentState || _currentState->_r[2] != currentState->_r[2];
	didR3Change = !_currentState || _currentState->_r[3] != currentState->_r[3];
	didR4Change = !_currentState || _currentState->_r[4] != currentState->_r[4];
	didR5Change = !_currentState || _currentState->_r[5] != currentState->_r[5];
	didR6Change = !_currentState || _currentState->_r[6] != currentState->_r[6];
	didR7Change = !_currentState || _currentState->_r[7] != currentState->_r[7];
	didUserModeChange = !_currentState || _currentState->u != currentState->u;
	didFlagChange = !_currentState || _currentState->f != currentState->f;
	didExecutingChange = !_currentState || _currentState->x != currentState->x;
	didRSerialChange = !_currentState || _currentState->s != currentState->s;
	didInstructionChange = !_currentState || _currentState->i != currentState->i;
	didExceptionStateChange = !_currentState || _currentState->e != currentState->e;

	if(didR0Change) [self willChangeValueForKey:@"r0"];
	if(didR1Change) [self willChangeValueForKey:@"r1"];
	if(didR2Change) [self willChangeValueForKey:@"r2"];
	if(didR3Change) [self willChangeValueForKey:@"r3"];
	if(didR4Change) [self willChangeValueForKey:@"r4"];
	if(didR5Change) [self willChangeValueForKey:@"r5"];
	if(didR6Change) [self willChangeValueForKey:@"r6"];
	if(didR7Change) [self willChangeValueForKey:@"r7"];
	if(didUserModeChange) [self willChangeValueForKey:@"userMode"];
	if(didFlagChange) [self willChangeValueForKey:@"flag"];
	if(didExecutingChange) [self willChangeValueForKey:@"executing"];
	if(didRSerialChange) [self willChangeValueForKey:@"rSerial"];
	if(didInstructionChange) [self willChangeValueForKey:@"instruction"];
	if(didExceptionStateChange) [self willChangeValueForKey:@"exceptionState"];
	
	_currentState = currentState;
	
	if(didExceptionStateChange) [self didChangeValueForKey:@"exceptionState"];
	if(didInstructionChange) [self didChangeValueForKey:@"instruction"];
	if(didRSerialChange) [self didChangeValueForKey:@"rSerial"];
	if(didExecutingChange) [self didChangeValueForKey:@"executing"];
	if(didFlagChange) [self didChangeValueForKey:@"flag"];
	if(didUserModeChange) [self didChangeValueForKey:@"userMode"];
	if(didR7Change) [self didChangeValueForKey:@"r7"];
	if(didR6Change) [self didChangeValueForKey:@"r6"];
	if(didR5Change) [self didChangeValueForKey:@"r5"];
	if(didR4Change) [self didChangeValueForKey:@"r4"];
	if(didR3Change) [self didChangeValueForKey:@"r3"];
	if(didR2Change) [self didChangeValueForKey:@"r2"];
	if(didR1Change) [self didChangeValueForKey:@"r1"];
	if(didR0Change) [self didChangeValueForKey:@"r0"];
}

- (struct state *)nextState
{
	edit_state(_machine);
	return get_state(_machine);
}

#pragma mark NSCoding protocol
- (void)encodeWithCoder:(NSCoder *)aCoder
{
	if(![aCoder allowsKeyedCoding]) {
		return;
	}
	
	size_t machine_size = save_machine(_machine, FILETYPE_COMPLETE, nil, 0);
	NSMutableData *machineData = [[NSMutableData alloc] initWithLength:machine_size];
	save_machine(_machine, FILETYPE_COMPLETE, (void *)[machineData bytes], machine_size);
	
	[aCoder encodeObject:machineData forKey:@"machine"];
	[machineData release];
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
	self = [super init];
	if(!self) {
		return nil;
	}
	
	NSData *machineData = [aDecoder decodeObjectForKey:@"machine"];
	_machine = load_machine(FILETYPE_COMPLETE, (void *)[machineData bytes], [machineData length]);
	if(!_machine) {
		[self dealloc];
		return nil;
	}

	[self setCurrentState:get_state(_machine)];
	recv_serial_bytes(_machine, OIEmulatorSerialPortCallback, self);
	_cyclesPerRTCTick = 10000;
	
	return self;
}

#pragma mark Actions
- (void)step
{
	[self stepCycles:1];
}

- (void)stepCycles:(int)cycles;
{
	// Trigger the RTC clock if necessary
	struct state *current_state = get_state(_machine);
	if(current_state->T % _cyclesPerRTCTick == _cyclesPerRTCTick - 1) {
		edit_state(_machine);
		cause_rtc(_machine);
	}
	
	NSMutableArray *addresses = [NSMutableArray array];
	while(cycles --> 0) {
		tick(_machine);
		current_state = get_state(_machine);
		if(current_state->w) {
			[addresses addObject:[NSNumber numberWithInt:current_state->a]];
		}
	}
	
	if([addresses count] > 0) {
		NSDictionary *infoDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
			addresses, @"addresses",
			nil];
		[[NSNotificationCenter defaultCenter] postNotificationName:OIEmulatorDidWriteMemoryNotificationName object:self userInfo:infoDictionary];
	}
	
	[self setCurrentState:current_state];
}

+ (NSSet *)keyPathsForValuesAffectingValueForCanUnstep
{
	return [NSSet setWithObject:@"currentState"];
}

- (BOOL)canUnstep
{
	return can_untick(_machine);
}

- (void)unstep
{
	BOOL hadWritten = _currentState->w;
	int32_t writeAddress = _currentState->a;

	untick(_machine);
	[self setCurrentState:get_state(_machine)];
	
	if(hadWritten) {
		NSDictionary *infoDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
			[NSArray arrayWithObject:[NSNumber numberWithInt:writeAddress]], @"addresses",
			nil];
		[[NSNotificationCenter defaultCenter] postNotificationName:OIEmulatorDidWriteMemoryNotificationName object:self userInfo:infoDictionary];
	}
}

- (void)bigRedButton
{
	struct state *nextState = [self nextState];
	cause_nmi(_machine);
	[self setCurrentState:nextState];
}

#pragma mark Accessors
- (int16_t)valueAtPhysicalAddress:(int32_t)address
{
	return p_mem_read(_machine, address, false);
}

- (void)setValue:(int16_t)value atPhysicalAddress:(int32_t)address
{
	struct state *nextState = [self nextState];
	p_mem_write(_machine, address, value, false);
	[self setCurrentState:nextState];
	
	NSDictionary *infoDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
		[NSArray arrayWithObject:[NSNumber numberWithInt:address]], @"addresses",
		nil];
	[[NSNotificationCenter defaultCenter] postNotificationName:OIEmulatorDidWriteMemoryNotificationName object:self userInfo:infoDictionary];

}

#pragma mark Registers
- (int16_t)r0
{
	return _currentState->_r[0];
}

- (void)setR0:(int16_t)value
{
	struct state *nextState = [self nextState];
	nextState->_r[0] = value;
	[self setCurrentState:nextState];
}

- (int16_t)r1
{
	return _currentState->_r[1];
}

- (void)setR1:(int16_t)value
{
	struct state *nextState = [self nextState];
	nextState->_r[1] = value;
	[self setCurrentState:nextState];
}

- (int16_t)r2
{
	return _currentState->_r[2];
}

- (void)setR2:(int16_t)value
{
	struct state *nextState = [self nextState];
	nextState->_r[2] = value;
	[self setCurrentState:nextState];
}

- (int16_t)r3
{
	return _currentState->_r[3];
}

- (void)setR3:(int16_t)value
{
	struct state *nextState = [self nextState];
	nextState->_r[3] = value;
	[self setCurrentState:nextState];
}

- (int16_t)r4
{
	return _currentState->_r[4];
}

- (void)setR4:(int16_t)value
{
	struct state *nextState = [self nextState];
	nextState->_r[4] = value;
	[self setCurrentState:nextState];
}

- (int16_t)r5
{
	return _currentState->_r[5];
}

- (void)setR5:(int16_t)value
{
	struct state *nextState = [self nextState];
	nextState->_r[5] = value;
	[self setCurrentState:nextState];
}

- (int16_t)r6
{
	return _currentState->_r[6];
}

- (void)setR6:(int16_t)value
{
	struct state *nextState = [self nextState];
	nextState->_r[6] = value;
	[self setCurrentState:nextState];
}

- (int16_t)r7
{
	return _currentState->_r[7];
}

- (void)setR7:(int16_t)value
{
	struct state *nextState = [self nextState];
	nextState->_r[7] = value;
	[self setCurrentState:nextState];
}

- (BOOL)isUserMode
{
	return _currentState->u;
}

- (void)setUserMode:(BOOL)flag
{
	struct state *nextState = [self nextState];
	nextState->u = flag;
	[self setCurrentState:nextState];
}

- (BOOL)isFlag
{
	return _currentState->f;
}

- (void)setFlag:(BOOL)flag
{
	struct state *nextState = [self nextState];
	nextState->f = flag;
	[self setCurrentState:nextState];
}

- (BOOL)isExecuting
{
	return _currentState->x;
}

- (void)setExecuting:(BOOL)flag
{
	struct state *nextState = [self nextState];
	nextState->x = flag;
	[self setCurrentState:nextState];
}

- (int16_t)rSerial
{
	return _currentState->s;
}

- (void)setRSerial:(int16_t)value
{
	struct state *nextState = [self nextState];
	nextState->s = value;
	[self setCurrentState:nextState];

}

- (int16_t)instruction
{
	return _currentState->i;
}

- (void)setInstruction:(int16_t)value
{
	struct state *nextState = [self nextState];
	nextState->i = value;
	[self setCurrentState:nextState];
}

- (int16_t)exceptionState
{
	return _currentState->e << 10;
}

- (void)setExceptionState:(int16_t)value
{
	struct state *nextState = [self nextState];
	nextState->e = value >> 10;
	[self setCurrentState:nextState];
}

#pragma mark Serial port
- (void)setSerialPortDelegate:(id)delegate
{
	if(delegate && ![delegate respondsToSelector:@selector(emulator:didReadByteFromSerialPort:)]) {
		return;
	}
	
	_serialPortDelegate = delegate;
}

- (void)readByteFromSerialPort:(uint8_t)byte
{
	if(!_serialPortDelegate) {
		return;
	}
	
	[_serialPortDelegate emulator:self didReadByteFromSerialPort:byte];
}

- (void)writeByteToSerialPort:(uint8_t)byte
{
	struct state *nextState = [self nextState];
	send_serial_byte(_machine, byte);
	[self setCurrentState:nextState];
}

@end
